import re
from pathlib import Path
from typing import Any, Dict

from pptx import Presentation

from ..loader.markdown_loader import load_markdown
from ..template.ppt_parser import open_template, find_slide_map, find_placeholders
from ..renderers import text as text_renderer
from ..renderers import list as list_renderer
from ..renderers import image as image_renderer
from ..renderers import table as table_renderer
from ..renderers import chart as chart_renderer


def substitute_text(shape, key: str, value: Any):
    text_renderer.render(shape, value)


def fill_list(shape, items):
    list_renderer.render(shape, items or [])


def fill_image(slide, shape, path):
    return image_renderer.render(slide, shape, path)


def fill_table(table_shape, data):
    return table_renderer.render(table_shape, data or {})


def fill_chart(chart_shape, data):
    return chart_renderer.render(chart_shape, data or {})


def is_list_key(key: str) -> bool:
    return key.startswith("list.")


def is_image_key(key: str) -> bool:
    return key.startswith("image.")


def is_table_key(key: str) -> bool:
    return key.startswith("table.")


def is_chart_key(key: str) -> bool:
    return key.startswith("chart.")


def render_markdown_text(value: str) -> str:
    lines = []
    for line in str(value).splitlines():
        if line.strip().startswith("-"):
            lines.append(line.strip()[1:].strip())
        else:
            lines.append(line)
    return "\n".join(lines)


def build(md_path: str, template_override: str = None, output_override: str = None):
    fm, pages = load_markdown(md_path)
    template_path = template_override or fm.get("template")
    output_path = output_override or fm.get("output")
    if not template_path or not output_path:
        raise RuntimeError("template or output missing")
    prs = open_template(template_path)
    slide_map = find_slide_map(prs)
    for page in pages:
        slide_id = page.get("template_slide") or page.get("slide")
        if slide_id not in slide_map:
            available = ", ".join(slide_map.keys()) or "<none>"
            raise RuntimeError(f"template slide not found: {slide_id}; available: {available}")
        slide = prs.slides[slide_map[slide_id]]
        placeholders = page.get("placeholders") or {}
        notes = page.get("notes")
        if notes:
            if not slide.has_notes_slide:
                slide.notes_slide
            slide.notes_slide.notes_text_frame.text = str(notes)
        ph_shapes = find_placeholders(slide)
        keys_to_shapes = {}
        for key, shp in ph_shapes:
            keys_to_shapes.setdefault(key, []).append(shp)
        for key, value in placeholders.items():
            if key.endswith(".md") and isinstance(value, str):
                value = render_markdown_text(value)
            shapes = keys_to_shapes.get(key) or []
            if is_list_key(key):
                for shp in shapes:
                    fill_list(shp, value)
            elif is_image_key(key):
                for shp in shapes:
                    fill_image(slide, shp, value)
            elif is_table_key(key):
                table_shape = None
                for s in slide.shapes:
                    if hasattr(s, "has_table") and s.has_table:
                        table_shape = s
                        break
                if table_shape:
                    fill_table(table_shape, value)
            elif is_chart_key(key):
                chart_shape = None
                for s in slide.shapes:
                    if hasattr(s, "has_chart") and s.has_chart:
                        chart_shape = s
                        break
                if chart_shape:
                    fill_chart(chart_shape, value)
            else:
                for shp in shapes:
                    substitute_text(shp, key, value)
        for key, shapes in keys_to_shapes.items():
            if key in placeholders:
                continue
            for shp in shapes:
                shp.text_frame.text = ""
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    prs.save(output_path)


def validate(md_path: str, template_override: str = None):
    fm, pages = load_markdown(md_path)
    template_path = template_override or fm.get("template")
    if not template_path:
        raise RuntimeError("template missing")
    prs = open_template(template_path)
    slide_map = find_slide_map(prs)
    for page in pages:
        slide_id = page.get("template_slide") or page.get("slide")
        if slide_id not in slide_map:
            raise RuntimeError(f"template slide not found: {slide_id}")
        placeholders = page.get("placeholders") or {}
        for key, value in placeholders.items():
            if is_image_key(key):
                p = Path(str(value))
                if not p.exists():
                    raise RuntimeError(f"image not found: {value}")